package net.luohuasheng.bee.proxy.mybatis.executor.processor;

import net.luohuasheng.bee.jdbc.tools.common.utils.ReflectionUtils;
import net.luohuasheng.bee.proxy.core.exception.ConventException;
import net.luohuasheng.bee.proxy.core.utils.convert.ValueConvertUtils;
import net.luohuasheng.bee.proxy.core.utils.jpa.JpaLoadUtils;
import net.luohuasheng.bee.proxy.core.utils.jpa.annotation.TableLogic;
import net.luohuasheng.bee.proxy.core.utils.jpa.dto.JpaColumnDto;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.session.Configuration;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class InsertProcessor extends BaseProcessor {

    private Map<Class<?>, List<FieldLogic>> fieldMaps = new HashMap<>();

    public InsertProcessor(Configuration configuration, Class<?> entityClass) {
        super(configuration, entityClass);
    }

    @Override
    public void rewrite(Invocation invocation, MappedStatement ms, BoundSql boundSql, Object parameter) throws Exception {
        if (parameter != null) {
            List<FieldLogic> fields = fieldMaps.computeIfAbsent(parameter.getClass(), v -> getFields(parameter.getClass()));
            for (FieldLogic field : fields) {
                field.getField().setAccessible(true);
                field.getField().set(parameter, ValueConvertUtils.convert(field.tableLogic.noDelFlag(), field.getField().getType()));
                field.getField().setAccessible(false);
            }
        }
    }

    private List<FieldLogic> getFields(Class<?> classZ) {
        List<FieldLogic> list = new ArrayList<>();
        List<Field> fields = ReflectionUtils.getAllFieldsExcludeTransient(classZ);
        for (Field field : fields) {
            try {
                PropertyDescriptor pd = new PropertyDescriptor(field.getName(), classZ);
                Method readMethod = pd.getReadMethod();
                Method writeMethod = pd.getWriteMethod();
                TableLogic logic = JpaLoadUtils.getAnnotation(readMethod, writeMethod, field, TableLogic.class);
                if (logic != null) {
                    list.add(new FieldLogic(field, logic));
                }
            } catch (IntrospectionException e) {
                e.printStackTrace();
            }
        }
        return list;
    }

    @Override
    public SqlSource createSqlSource() {
        return createSqlSource(insertSql());
    }

    private String insertSql() {
        SQL sql = new SQL().INSERT_INTO(jpaTableDto.getName());
        List<JpaColumnDto> columns = jpaTableDto.getColumns().stream().filter(JpaColumnDto::isInsertable).collect(Collectors.toList());
        for (JpaColumnDto columnDto : columns) {
            if ((columnDto.getStrategy() != null && columnDto.isPk())) {
                sql.VALUES(columnDto.getName(), "<if test=\"" + columnDto.getField().getName() + " != null\">#{" + columnDto.getField().getName() + "}</if>" + "<if test=\"" + columnDto.getField().getName() + " == null\">null</if>");
            } else {
                sql.VALUES(columnDto.getName(), "#{" + columnDto.getField().getName() + "}");
            }
        }

        return sql.toString().replaceAll("\n", " ");
    }

    @Override
    public KeyGenerator keyGenerator() {
        return getKey().getKeyGenerator();
    }

    @Override
    public String keyProperty() {
        return getKey().getKeyProperty();
    }

    @Override
    public String keyColumn() {
        return getKey().getKeyProperty();
    }

    static class FieldLogic {
        private Field field;
        private TableLogic tableLogic;

        public FieldLogic(Field field, TableLogic tableLogic) {
            this.field = field;
            this.tableLogic = tableLogic;
        }

        public Field getField() {
            return field;
        }


        public TableLogic getTableLogic() {
            return tableLogic;
        }

    }
}
